﻿// 引入必要的命名空间
using SqlSugar;                                                                    // SqlSugar ORM框架，用于数据库操作
using SqlsugarService.Application.DTOs.Process;                                   // 工序相关的数据传输对象
using SqlsugarService.Application.IService.Process.ProcessComposition;            // 工序组成服务接口
using SqlsugarService.Application.Until;                                          // 通用工具类，如ApiResult、PageResult等
using SqlsugarService.Domain.BOM;                                                 // BOM相关的实体类
using SqlsugarService.Domain.Craftsmanship;                                       // 工艺相关的实体类
using SqlsugarService.Domain.Materials;                                           // 物料相关的实体类
using SqlsugarService.Infrastructure.DbContext;                                   // 数据库上下文
using SqlsugarService.Infrastructure.IRepository;                                 // 仓储接口
using System;                                                                      // 系统基础类型
using System.Collections.Generic;                                                 // 集合类型
using System.ComponentModel.DataAnnotations;                                      // 数据验证注解
using System.Linq;                                                                // LINQ查询扩展
using System.Threading.Tasks;                                                     // 异步编程支持

namespace SqlsugarService.Application.Service.Process.ProcessComposition
{
    /// <summary>
    /// 工序组成服务实现类
    /// 
    /// 这个类是整个工艺流程管理的核心服务，主要负责：
    /// 1. 工艺路线管理：创建和查询工艺路线
    /// 2. 工序管理：创建和查询工序
    /// 3. 工序组成管理：将工序添加到工艺路线中，形成完整的工艺流程
    /// 4. 工序物料详情管理：管理每个工序需要的投入物料和产出物料
    /// 
    /// 业务流程说明：
    /// 工艺路线 → 包含多个工序 → 每个工序有物料详情（投入/产出）
    /// 例如：手机生产工艺路线 → 包含（组装工序、测试工序、包装工序）→ 每个工序都有对应的物料需求
    /// </summary>
    public class ProcessCompositionService : IProcessCompositionService
    {
        #region 依赖注入 - 这里定义了服务需要的所有外部依赖

        // 私有只读字段：存储通过依赖注入获得的服务实例
        // 为什么使用private readonly？确保这些服务实例在对象创建后不能被修改，保证线程安全
        
        /// <summary>
        /// 数据库上下文 - 直接操作数据库的核心对象
        /// 作用：执行复杂的SQL查询、多表关联、事务处理等
        /// 为什么需要？Repository模式通常只提供简单的CRUD操作，复杂业务查询需要直接使用DbContext
        /// </summary>
        private readonly SqlSugarDbContext _dbContext;
        
        /// <summary>
        /// 工序组成仓储 - 管理工序组成数据的增删改查
        /// 作用：处理ProcessRouteStep表的基础数据操作
        /// 业务含义：管理"哪个工艺路线包含哪些工序"的关系数据
        /// </summary>
        private readonly IBaseRepository<ProcessRouteStep> _processCompositionRepository;
        
        /// <summary>
        /// 工艺路线仓储 - 管理工艺路线数据的增删改查
        /// 作用：处理ProcessRouteEntity表的基础数据操作
        /// 业务含义：管理工艺路线的基本信息（编号、名称、状态等）
        /// </summary>
        private readonly IBaseRepository<ProcessRouteEntity> _processRouteRepository;
        
        /// <summary>
        /// 工序仓储 - 管理工序数据的增删改查
        /// 作用：处理ProcessStep表的基础数据操作
        /// 业务含义：管理工序的基本信息（编号、名称、状态、时间等）
        /// </summary>
        private readonly IBaseRepository<ProcessStep> _processStepRepository;
        
        /// <summary>
        /// 工序物料详情仓储 - 管理工序物料详情数据的增删改查
        /// 作用：处理ProcessStepMaterialDetail表的基础数据操作
        /// 业务含义：管理每个工序需要的物料信息（投入物料、产出物料、用量等）
        /// </summary>
        private readonly IBaseRepository<ProcessStepMaterialDetail> _materialDetailRepository;

        /// <summary>
        /// 构造函数 - 服务类的初始化方法
        /// 
        /// 依赖注入原理解释：
        /// 1. 不在类内部直接创建依赖对象，而是通过构造函数参数接收
        /// 2. 由IoC容器（如.NET Core的内置容器）负责创建和注入这些依赖
        /// 3. 好处：降低耦合度、便于单元测试、便于替换实现
        /// 
        /// 为什么需要这么多Repository？
        /// 1. 单一职责原则：每个Repository只负责一个实体的数据操作
        /// 2. 业务复杂性：工艺流程涉及多个相关联的实体，需要协调操作
        /// 3. 数据一致性：通过事务确保多表操作的一致性
        /// </summary>
        /// <param name="dbContext">数据库上下文 - 用于复杂查询和事务处理</param>
        /// <param name="processCompositionRepository">工序组成仓储 - 管理工序与工艺路线的关系</param>
        /// <param name="processRouteRepository">工艺路线仓储 - 管理工艺路线基础数据</param>
        /// <param name="processStepRepository">工序仓储 - 管理工序基础数据</param>
        /// <param name="materialDetailRepository">工序物料详情仓储 - 管理工序的物料需求</param>
        public ProcessCompositionService(
            SqlSugarDbContext dbContext,
            IBaseRepository<ProcessRouteStep> processCompositionRepository,
            IBaseRepository<ProcessRouteEntity> processRouteRepository,
            IBaseRepository<ProcessStep> processStepRepository,
            IBaseRepository<ProcessStepMaterialDetail> materialDetailRepository)
        {
            // 空值检查：确保所有依赖都不为空，如果为空则抛出异常
            // ?? 是空合并运算符，如果左边为null，则执行右边的表达式
            // throw new ArgumentNullException() 会抛出参数为空异常，程序会立即停止
            _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
            _processCompositionRepository = processCompositionRepository ?? throw new ArgumentNullException(nameof(processCompositionRepository));
            _processRouteRepository = processRouteRepository ?? throw new ArgumentNullException(nameof(processRouteRepository));
            _processStepRepository = processStepRepository ?? throw new ArgumentNullException(nameof(processStepRepository));
            _materialDetailRepository = materialDetailRepository ?? throw new ArgumentNullException(nameof(materialDetailRepository));
        }

        #endregion

        #region 工艺路线管理 - 管理工艺路线的创建和查询

        /// <summary>
        /// 新增工艺路线 - 创建一个新的工艺路线
        /// 
        /// 业务场景举例：
        /// 比如要创建一个"手机组装工艺路线"，包含编号、名称、状态等信息
        /// 这个工艺路线后续可以添加多个工序（如：贴膜→组装→测试→包装）
        /// 
        /// 主要验证逻辑：
        /// 1. 数据格式验证（必填字段、长度限制等）
        /// 2. 业务规则验证（编号不能重复、状态必须合法等）
        /// 3. 版本管理（如果是新版本，需要处理旧版本状态）
        /// </summary>
        /// <param name="dto">工艺路线数据传输对象 - 包含要创建的工艺路线信息</param>
        /// <returns>操作结果 - 成功返回Success，失败返回具体错误信息</returns>
        public async Task<ApiResult> AddProcessRouteAsync(ProcessRouteDto dto)
        {
            try // try-catch用于捕获和处理可能出现的异常，确保程序不会崩溃
            {
                // 步骤1：数据验证 - 检查传入的数据是否符合要求
                // 调用私有方法ValidateProcessRouteDto进行详细验证
                var validationResult = await ValidateProcessRouteDto(dto);
                if (!validationResult.IsSuc) // IsSuc表示是否成功，如果验证失败则直接返回错误
                {
                    return validationResult; // 返回验证失败的具体错误信息
                }

                // 步骤2：验证工艺路线编号是否重复 - 确保编号的唯一性
                // 使用SqlSugar的Queryable方法查询数据库
                var existingRoute = await _dbContext.Db.Queryable<ProcessRouteEntity>()
                    .Where(pr => pr.ProcessRouteNumber == dto.ProcessRouteNumber) // 查找相同编号的记录
                    .FirstAsync(); // 获取第一条记录，如果没有则返回null

                if (existingRoute != null) // 如果找到了相同编号的记录
                {
                    // 返回失败结果，告诉用户编号已存在
                    return ApiResult.Fail($"工艺路线编号 {dto.ProcessRouteNumber} 已存在", ResultCode.AlreadyExists);
                }

                // 步骤3：版本管理验证 - 如果指定了上一版本ID，需要验证该版本是否存在
                // HasValue检查可空类型是否有值，Value获取可空类型的实际值
                //if (dto.PreviousVersionId.HasValue && dto.PreviousVersionId.Value != Guid.Empty)
                //{
                //    // 通过Repository查询上一版本是否存在
                //    var previousVersion = await _processRouteRepository.GetByIdAsync(dto.PreviousVersionId.Value);
                //    if (previousVersion == null) // 如果上一版本不存在
                //    {
                //        return ApiResult.Fail("指定的上一版本工艺路线不存在", ResultCode.NotFound);
                //    }
                //}

                // 步骤4：创建工艺路线实体对象 - 将DTO转换为数据库实体
                // 实体对象包含了数据库表的所有字段
                var processRoute = new ProcessRouteEntity
                {
                    Id = Guid.NewGuid(),                                    // 生成唯一标识符
                    ProcessRouteNumber = dto.ProcessRouteNumber.Trim(),     // 去除前后空格
                    ProcessRouteName = dto.ProcessRouteName.Trim(),        // 去除前后空格
                    Status = dto.Status.Trim(),                            // 去除前后空格
                    Description = dto.Description?.Trim(),                 // ?. 是空条件运算符，如果为null则不执行Trim()
                    Remark = dto.Remark?.Trim(),                          // 同上
                    IsActive = dto.IsActive,                               // 是否为活动版本
                    VersionDescription = dto.VersionDescription?.Trim(),   // 版本说明
                    PreviousVersionId = dto.PreviousVersionId,             // 上一版本ID
                    //CreatedTime = DateTime.Now,                            // 创建时间设为当前时间
                    //LastUpdatedTime = DateTime.Now,                        // 最后更新时间设为当前时间
                    CreatedAt = DateTime.Now,                              // 创建时间（另一个字段）
                    CreatedBy = "system"                                   // 创建者（实际项目中应该是当前登录用户）
                };

                // 步骤5：保存到数据库 - 使用Repository的InsertAsync方法
                // InsertAsync返回bool值，true表示插入成功，false表示插入失败
                var insertResult = await _processRouteRepository.InsertAsync(processRoute);
                if (!insertResult) // 如果插入失败
                {
                    return ApiResult.Fail("保存工艺路线失败", ResultCode.Error);
                }

                // 步骤6：版本管理 - 如果设置为当前活动版本，需要将其他版本设为非活动状态
                // 这样确保同一编号的工艺路线只有一个活动版本
                if (dto.IsActive && dto.PreviousVersionId.HasValue)
                {
                    // 调用私有方法更新其他版本的状态
                    await UpdateOtherProcessRouteVersionsToInactive(dto.ProcessRouteNumber, processRoute.Id);
                }

                // 返回成功结果
                return ApiResult.Success(ResultCode.Success);
            }
            catch (Exception ex) // 捕获所有异常
            {
                // 返回失败结果，包含异常信息
                // 实际项目中应该记录日志，而不是直接返回异常信息给用户
                return ApiResult.Fail($"添加工艺路线失败: {ex.Message}", ResultCode.Error);
            }
        }

        /// <summary>
        /// 分页获取工艺路线列表 - 查询工艺路线数据并分页显示
        /// 
        /// 分页的作用：
        /// 当数据库中有大量工艺路线时（比如几千条），一次性查询所有数据会：
        /// 1. 消耗大量内存
        /// 2. 网络传输慢
        /// 3. 前端页面卡顿
        /// 
        /// 分页解决方案：
        /// 每次只查询一页的数据（比如10条），用户可以翻页查看其他数据
        /// 
        /// 业务场景：
        /// 用户在管理界面查看所有工艺路线，可以按状态筛选，支持翻页浏览
        /// </summary>
        /// <param name="status">状态过滤条件 - 如"启用"、"禁用"，为空则查询所有状态</param>
        /// <param name="pageIndex">页码 - 从1开始，表示要查询第几页</param>
        /// <param name="pageSize">每页大小 - 每页显示多少条记录</param>
        /// <returns>分页结果 - 包含当前页数据、总记录数、总页数等信息</returns>
        public async Task<ApiResult<PageResult<List<ProcessRouteListDto>>>> GetProcessRoutePagedAsync(
            string? status, int pageIndex, int pageSize)
        {
            try
            {
                // 步骤1：参数验证 - 确保传入的分页参数合理
                if (pageIndex < 1) // 页码必须从1开始
                {
                    return ApiResult<PageResult<List<ProcessRouteListDto>>>.Fail(
                        "页码必须大于0", ResultCode.ValidationError);
                }

                if (pageSize < 1 || pageSize > 1000) // 每页大小必须在合理范围内
                {
                    return ApiResult<PageResult<List<ProcessRouteListDto>>>.Fail(
                        "每页大小必须在1-1000之间", ResultCode.ValidationError);
                }

                // 步骤2：构建基础查询 - 创建查询对象
                // Queryable<T>()创建一个可查询的对象，类似于SQL的SELECT * FROM ProcessRouteEntity
                var query = _dbContext.Db.Queryable<ProcessRouteEntity>();

                // 条件过滤：如果指定了状态，则添加WHERE条件
                if (!string.IsNullOrWhiteSpace(status)) // 检查状态参数是否有值且不为空白
                {
                    // 添加WHERE条件，相当于SQL: WHERE Status = '启用'
                    query = query.Where(pr => pr.Status == status.Trim());
                }

                // 步骤3：获取符合条件的记录总数 - 用于计算总页数
                // CountAsync()相当于SQL: SELECT COUNT(*) FROM ProcessRouteEntity WHERE ...
                var totalCount = await query.CountAsync();

                // 步骤4：执行分页查询 - 获取当前页的数据
                var routes = await query
                    .OrderByDescending(pr => pr.CreatedAt)  // 按创建时间倒序排列（最新的在前面）
                    .Skip((pageIndex - 1) * pageSize)        // 跳过前面的记录，Skip相当于SQL的OFFSET
                    .Take(pageSize)                          // 取指定数量的记录，Take相当于SQL的LIMIT
                    .ToListAsync();                          // 执行查询并转换为List

                // 分页计算解释：
                // 假设pageIndex=2, pageSize=10
                // Skip((2-1)*10) = Skip(10) 跳过前10条记录
                // Take(10) 取接下来的10条记录
                // 这样就获得了第2页的数据

                // 步骤5：获取每个工艺路线包含的工序数量 - 提供额外的统计信息
                var routeIds = routes.Select(r => r.Id).ToList(); // 提取所有工艺路线的ID
                
                // 查询工序组成表，统计每个工艺路线包含多少个工序
                var stepCounts = await _dbContext.Db.Queryable<ProcessRouteStep>()
                    .Where(prs => routeIds.Contains(prs.ProcessRouteId))    // 只查询当前页工艺路线的工序
                    .GroupBy(prs => prs.ProcessRouteId)                     // 按工艺路线ID分组
                    .Select(g => new { 
                        ProcessRouteId = g.ProcessRouteId,                  // 工艺路线ID
                        Count = SqlFunc.AggregateCount(g.ProcessRouteId)    // 统计每组的数量
                    })
                    .ToListAsync();

                // 将统计结果转换为字典，方便后续查找
                // ToDictionary创建一个字典，key是工艺路线ID，value是工序数量
                var stepCountDict = stepCounts.ToDictionary(sc => sc.ProcessRouteId, sc => sc.Count);

                // 步骤6：数据映射 - 将实体对象转换为DTO对象
                // 为什么要转换？实体对象包含所有数据库字段，DTO只包含前端需要的字段
                var data = routes.Select(route => new ProcessRouteListDto
                {
                    Id = route.Id,
                    ProcessRouteNumber = route.ProcessRouteNumber ?? string.Empty,  // ?? 空合并运算符，如果为null则使用空字符串
                    ProcessRouteName = route.ProcessRouteName ?? string.Empty,
                    Status = route.Status ?? string.Empty,
                    Description = route.Description,
                    Remark = route.Remark,
                    IsActive = route.IsActive,
                    VersionDescription = route.VersionDescription,
                    PreviousVersionId = route.PreviousVersionId,
                    //CreatedTime = route.CreatedTime,
                    //LastUpdatedTime = route.LastUpdatedTime,
                    // GetValueOrDefault：如果字典中存在该key则返回对应value，否则返回默认值0
                    ProcessStepCount = stepCountDict.GetValueOrDefault(route.Id, 0)
                }).ToList();

                // 步骤7：构建分页结果对象 - 封装分页信息和数据
                var pageResult = new PageResult<List<ProcessRouteListDto>>
                {
                    Data = data,                                                    // 当前页的数据
                    TotalCount = totalCount,                                        // 总记录数
                    TotalPage = (int)Math.Ceiling(totalCount * 1.0 / pageSize)    // 总页数（向上取整）
                };

                // Math.Ceiling向上取整的原因：
                // 假设总记录数23，每页10条
                // 23 / 10 = 2.3，向上取整得到3页
                // 这样确保所有数据都能被显示

                return ApiResult<PageResult<List<ProcessRouteListDto>>>.Success(pageResult, ResultCode.Success);
            }
            catch (Exception ex)
            {
                return ApiResult<PageResult<List<ProcessRouteListDto>>>.Fail(
                    $"获取工艺路线列表失败: {ex.Message}", ResultCode.Error);
            }
        }

        #endregion

        #region 工序管理 - 管理单个工序的创建和查询

        /// <summary>
        /// 新增工序 - 创建一个新的工序
        /// 
        /// 工序的概念：
        /// 工序是生产过程中的一个步骤，比如：
        /// - 手机生产：贴膜工序、组装工序、测试工序、包装工序
        /// - 汽车生产：焊接工序、喷漆工序、组装工序、检验工序
        /// 
        /// 工序的作用：
        /// 1. 定义生产步骤的基本信息（名称、编号、时间等）
        /// 2. 可以被多个工艺路线重复使用
        /// 3. 每个工序可以定义需要的物料（投入和产出）
        /// 
        /// 业务验证：
        /// 1. 工序编号不能重复（全局唯一）
        /// 2. 如果关联了子BOM，需要验证BOM是否存在
        /// 3. 数据格式验证（必填字段、长度限制等）
        /// </summary>
        /// <param name="dto">工序数据传输对象 - 包含要创建的工序信息</param>
        /// <returns>操作结果 - 成功或失败信息</returns>
        public async Task<ApiResult> AddProcessStepAsync(ProcessStepDto dto)
        {
            try
            {
                // 步骤1：数据验证
                var validationResult = await ValidateProcessStepDto(dto);
                if (!validationResult.IsSuc)
                {
                    return validationResult;
                }

                // 步骤2：验证工序编号是否重复
                var existingStep = await _dbContext.Db.Queryable<ProcessStep>()
                    .Where(ps => ps.ProcessStepNumber == dto.ProcessStepNumber)
                    .FirstAsync();

                if (existingStep != null)
                {
                    return ApiResult.Fail($"工序编号 {dto.ProcessStepNumber} 已存在", ResultCode.AlreadyExists);
                }

                // 步骤3：如果指定了子BOM ID，验证是否存在
                if (dto.SubBomId.HasValue && dto.SubBomId.Value != Guid.Empty)
                {
                    var bomExists = await _dbContext.Db.Queryable<BomInfo>()
                        .Where(b => b.Id == dto.SubBomId.Value)
                        .AnyAsync();

                    if (!bomExists)
                    {
                        return ApiResult.Fail("指定的子BOM不存在", ResultCode.NotFound);
                    }
                }

                // 步骤4：创建工序实体对象
                var processStep = new ProcessStep
                {
                    Id = Guid.NewGuid(),
                    ProcessStepNumber = dto.ProcessStepNumber.Trim(),
                    ProcessStepName = dto.ProcessStepName.Trim(),
                    Status = dto.Status,
                    Description = dto.Description?.Trim(),
                    Remark = dto.Remark?.Trim(),
                    IsSystemNumber = dto.IsSystemNumber,
                    IsKeyStep = dto.IsKeyStep,
                    PreparationTime = dto.PreparationTime,
                    WaitingTime = dto.WaitingTime,
                    Color = dto.Color?.Trim(),
                    SubBomId = dto.SubBomId,
                    CreatedAt = DateTime.Now,
                    CreatedBy = "system"
                };

                // 步骤5：保存到数据库
                var insertResult = await _processStepRepository.InsertAsync(processStep);
                if (!insertResult)
                {
                    return ApiResult.Fail("保存工序失败", ResultCode.Error);
                }

                return ApiResult.Success(ResultCode.Success);
            }
            catch (Exception ex)
            {
                return ApiResult.Fail($"添加工序失败: {ex.Message}", ResultCode.Error);
            }
        }

        /// <summary>
        /// 分页获取工序列表
        /// 功能：根据条件查询工序列表，支持分页显示和状态过滤
        /// </summary>
        /// <param name="status">状态过滤，如果为空则获取所有状态</param>
        /// <param name="pageIndex">页码，从1开始</param>
        /// <param name="pageSize">每页显示的记录数</param>
        /// <returns>分页的工序列表</returns>
        public async Task<ApiResult<PageResult<List<ProcessStepListDto>>>> GetProcessStepPagedAsync(
            StepStatus? status, int pageIndex, int pageSize)
        {
            try
            {
                // 步骤1：参数验证
                if (pageIndex < 1)
                {
                    return ApiResult<PageResult<List<ProcessStepListDto>>>.Fail(
                        "页码必须大于0", ResultCode.ValidationError);
                }

                if (pageSize < 1 || pageSize > 1000)
                {
                    return ApiResult<PageResult<List<ProcessStepListDto>>>.Fail(
                        "每页大小必须在1-1000之间", ResultCode.ValidationError);
                }

                // 步骤2：构建基础查询
                var query = _dbContext.Db.Queryable<ProcessStep>();

                // 如果指定了状态，则添加过滤条件
                if (status.HasValue)
                {
                    query = query.Where(ps => ps.Status == status.Value);
                }

                // 步骤3：获取符合条件的记录总数
                var totalCount = await query.CountAsync();

                // 步骤4：执行分页查询
                var steps = await query
                    .OrderByDescending(ps => ps.CreatedAt)  // 按创建时间倒序
                    .Skip((pageIndex - 1) * pageSize)
                    .Take(pageSize)
                    .ToListAsync();

                // 步骤5：获取每个工序的使用次数
                var stepIds = steps.Select(s => s.Id).ToList();
                var usageCounts = await _dbContext.Db.Queryable<ProcessRouteStep>()
                    .Where(prs => stepIds.Contains(prs.ProcessStepId))
                    .GroupBy(prs => prs.ProcessStepId)
                    .Select(prs => new { ProcessStepId = prs.ProcessStepId, Count = SqlFunc.AggregateCount(prs.ProcessStepId) })
                    .ToListAsync();

                var usageCountDict = usageCounts.ToDictionary(uc => uc.ProcessStepId, uc => uc.Count);

                // 步骤6：映射到DTO
                var data = steps.Select(step => new ProcessStepListDto
                {
                    Id = step.Id,
                    ProcessStepNumber = step.ProcessStepNumber ?? string.Empty,
                    ProcessStepName = step.ProcessStepName ?? string.Empty,
                    Status = step.Status,
                    StatusName = GetStepStatusName(step.Status),
                    Description = step.Description,
                    Remark = step.Remark,
                    IsSystemNumber = step.IsSystemNumber,
                    IsKeyStep = step.IsKeyStep,
                    PreparationTime = step.PreparationTime,
                    WaitingTime = step.WaitingTime,
                    Color = step.Color,
                    SubBomId = step.SubBomId,
                    CreatedAt = step.CreatedAt,
                    UsageCount = usageCountDict.GetValueOrDefault(step.Id, 0)
                }).ToList();

                // 步骤7：构建分页结果对象
                var pageResult = new PageResult<List<ProcessStepListDto>>
                {
                    Data = data,
                    TotalCount = totalCount,
                    TotalPage = (int)Math.Ceiling(totalCount * 1.0 / pageSize)
                };

                return ApiResult<PageResult<List<ProcessStepListDto>>>.Success(pageResult, ResultCode.Success);
            }
            catch (Exception ex)
            {
                return ApiResult<PageResult<List<ProcessStepListDto>>>.Fail(
                    $"获取工序列表失败: {ex.Message}", ResultCode.Error);
            }
        }

        #endregion

        #region 工序组成管理 - 管理工序与工艺路线的关系

        /// <summary>
        /// 新增工序组成 - 将工序添加到工艺路线中
        /// 
        /// 工序组成的概念：
        /// 工序组成是工艺路线和工序之间的关系，表示"某个工艺路线包含哪些工序"
        /// 
        /// 举例说明：
        /// 工艺路线：手机组装工艺路线
        /// 工序组成关系：
        /// - 第1步：贴膜工序
        /// - 第2步：组装工序  
        /// - 第3步：测试工序
        /// - 第4步：包装工序
        /// 
        /// 重要属性：
        /// - StepOrder：工序顺序号，决定工序的执行顺序
        /// - Version：版本号，支持工艺路线的版本管理
        /// - IsActive：是否活动版本，确保同一版本只有一个活动状态
        /// 
        /// 业务规则：
        /// 1. 同一工艺路线中，同一工序在同一版本下不能重复
        /// 2. 同一工艺路线的同一版本中，工序顺序号不能重复
        /// 3. 工艺路线和工序都必须是启用状态才能建立关系
        /// 4. 版本管理：设为活动版本时，其他版本自动变为非活动
        /// </summary>
        /// <param name="dto">工序组成数据传输对象 - 包含工艺路线ID、工序ID、顺序号等信息</param>
        /// <returns>操作结果 - 成功或失败信息</returns>
        public async Task<ApiResult> AddProcessCompositionAsync(ProcessCompositionDto dto)
        {
            try
            {
                // 步骤1：数据验证
                var validationResult = await ValidateProcessCompositionDto(dto);
                if (!validationResult.IsSuc)
                {
                    return validationResult;
                }

                // 步骤2：验证工艺路线是否存在且状态有效
                var processRoute = await _processRouteRepository.GetByIdAsync(dto.ProcessRouteId);
                if (processRoute == null)
                {
                    return ApiResult.Fail("指定的工艺路线不存在", ResultCode.NotFound);
                }

                // 检查工艺路线状态是否为启用状态
                if (processRoute.Status != "启用")
                {
                    return ApiResult.Fail("工艺路线未启用，无法添加工序组成", ResultCode.ValidationError);
                }

                // 步骤3：验证工序是否存在且状态有效
                var processStep = await _processStepRepository.GetByIdAsync(dto.ProcessStepId);
                if (processStep == null)
                {
                    return ApiResult.Fail("指定的工序不存在", ResultCode.NotFound);
                }

                // 检查工序状态是否为启用状态
                if (processStep.Status != StepStatus.Enabled)
                {
                    return ApiResult.Fail("工序未启用，无法添加到工艺路线", ResultCode.ValidationError);
                }

                // 步骤4：验证工序组成是否已存在
                // 同一个工艺路线中，同一个工序在同一版本下不能重复添加
                var existingComposition = await _dbContext.Db.Queryable<ProcessRouteStep>()
                    .Where(prs => prs.ProcessRouteId == dto.ProcessRouteId 
                               && prs.ProcessStepId == dto.ProcessStepId 
                               && prs.Version == dto.Version)
                    .FirstAsync();

                if (existingComposition != null)
                {
                    return ApiResult.Fail($"该工序在版本 {dto.Version} 中已存在于此工艺路线", ResultCode.AlreadyExists);
                }

                // 步骤5：验证工序顺序号是否重复
                // 同一个工艺路线的同一版本中，工序顺序号不能重复
                var existingOrder = await _dbContext.Db.Queryable<ProcessRouteStep>()
                    .Where(prs => prs.ProcessRouteId == dto.ProcessRouteId 
                               && prs.Version == dto.Version 
                               && prs.StepOrder == dto.StepOrder)
                    .AnyAsync();

                if (existingOrder)
                {
                    return ApiResult.Fail($"工序顺序号 {dto.StepOrder} 在版本 {dto.Version} 中已被使用", ResultCode.ValidationError);
                }

                // 步骤6：创建工序组成实体对象
                var processComposition = new ProcessRouteStep
                {
                    Id = Guid.NewGuid(),                    // 生成唯一ID
                    ProcessRouteId = dto.ProcessRouteId,    // 工艺路线ID
                    ProcessStepId = dto.ProcessStepId,      // 工序ID
                    StepOrder = dto.StepOrder,              // 工序顺序号
                    Version = dto.Version.Trim(),           // 版本号（去除前后空格）
                    IsActive = dto.IsActive,                // 是否当前有效版本
                    Remark = dto.Remark?.Trim(),            // 备注（去除前后空格）
                    CreatedAt = DateTime.Now,               // 创建时间
                    CreatedBy = "system"                    // 创建者（实际应该是当前登录用户）
                };

                // 步骤7：将工序组成保存到数据库
                var insertResult = await _processCompositionRepository.InsertAsync(processComposition);
                if (!insertResult)
                {
                    return ApiResult.Fail("保存工序组成失败", ResultCode.Error);
                }

                // 步骤8：如果设置为当前有效版本，需要将同一工艺路线下的其他版本设为非活动状态
                if (dto.IsActive)
                {
                    await UpdateOtherVersionsToInactive(dto.ProcessRouteId, dto.Version);
                }

                // 步骤9：返回成功结果
                return ApiResult.Success(ResultCode.Success);
            }
            catch (Exception ex)
            {
                // 如果发生异常，返回失败结果和错误信息
                return ApiResult.Fail($"添加工序组成失败: {ex.Message}", ResultCode.Error);
            }
        }

        /// <summary>
        /// 分页获取工序组成列表
        /// 功能：根据条件获取工序组成列表，支持分页显示，包含关联的工艺路线和工序信息
        /// </summary>
        /// <param name="processRouteId">工艺路线ID，如果为空则获取所有工序组成</param>
        /// <param name="pageIndex">页码，从1开始</param>
        /// <param name="pageSize">每页显示的记录数</param>
        /// <returns>分页的工序组成列表</returns>
        public async Task<ApiResult<PageResult<List<ProcessCompositionListDto>>>> GetProcessCompositionPagedAsync(
            Guid? processRouteId, int pageIndex, int pageSize)
        {
            try
            {
                // 步骤1：参数验证
                if (pageIndex < 1)
                {
                    return ApiResult<PageResult<List<ProcessCompositionListDto>>>.Fail(
                        "页码必须大于0", ResultCode.ValidationError);
                }

                if (pageSize < 1 || pageSize > 1000)
                {
                    return ApiResult<PageResult<List<ProcessCompositionListDto>>>.Fail(
                        "每页大小必须在1-1000之间", ResultCode.ValidationError);
                }

                // 步骤2：构建基础查询
                var query = _dbContext.Db.Queryable<ProcessRouteStep>();

                // 如果指定了工艺路线ID，则添加过滤条件
                if (processRouteId.HasValue && processRouteId.Value != Guid.Empty)
                {
                    query = query.Where(prs => prs.ProcessRouteId == processRouteId.Value);
                }

                // 步骤3：获取符合条件的记录总数
                var totalCount = await query.CountAsync();

                // 步骤4：执行分页查询并关联相关表信息
                var data = await query
                    // 左连接工艺路线表，获取工艺路线信息
                    .LeftJoin<ProcessRouteEntity>((prs, pr) => prs.ProcessRouteId == pr.Id)
                    // 左连接工序表，获取工序信息
                    .LeftJoin<ProcessStep>((prs, pr, ps) => prs.ProcessStepId == ps.Id)
                    // 选择需要返回的字段，映射到ProcessCompositionListDto
                    .Select((prs, pr, ps) => new ProcessCompositionListDto
                    {
                        Id = prs.Id,                                                    // 工序组成ID
                        ProcessRouteId = prs.ProcessRouteId,                           // 工艺路线ID
                        ProcessRouteName = pr.ProcessRouteName ?? string.Empty,        // 工艺路线名称
                        ProcessRouteNumber = pr.ProcessRouteNumber ?? string.Empty,    // 工艺路线编号
                        ProcessStepId = prs.ProcessStepId,                             // 工序ID
                        ProcessStepName = ps.ProcessStepName ?? string.Empty,          // 工序名称
                        ProcessStepNumber = ps.ProcessStepNumber ?? string.Empty,      // 工序编号
                        StepOrder = prs.StepOrder,                                     // 工序顺序号
                        Version = prs.Version ?? string.Empty,                        // 版本号
                        IsActive = prs.IsActive,                                       // 是否当前有效版本
                        Remark = prs.Remark,                                          // 备注
                        CreatedAt = prs.CreatedAt                                      // 创建时间
                    })
                    // 排序：先按工艺路线编号，再按版本号，最后按工序顺序号
                    .OrderBy(dto => dto.ProcessRouteNumber)
                    .OrderBy(dto => dto.Version)
                    .OrderBy(dto => dto.StepOrder)
                    // 分页处理
                    .Skip((pageIndex - 1) * pageSize)                              // 跳过前面的记录
                    .Take(pageSize)                                                // 取指定数量的记录
                    .ToListAsync();                                                // 异步执行查询

                // 步骤5：构建分页结果对象
                var pageResult = new PageResult<List<ProcessCompositionListDto>>
                {
                    Data = data,                                                    // 当前页的数据
                    TotalCount = totalCount,                                        // 总记录数
                    TotalPage = (int)Math.Ceiling(totalCount * 1.0 / pageSize)    // 总页数（向上取整）
                };

                // 步骤6：返回成功结果
                return ApiResult<PageResult<List<ProcessCompositionListDto>>>.Success(pageResult, ResultCode.Success);
            }
            catch (Exception ex)
            {
                // 如果查询失败，返回错误信息
                return ApiResult<PageResult<List<ProcessCompositionListDto>>>.Fail(
                    $"获取工序组成列表失败: {ex.Message}", ResultCode.Error);
            }
        }

        #endregion

        #region 工序物料详情管理 - 管理每个工序需要的物料信息

        /// <summary>   
        /// 单个添加工序物料详情 - 为工序添加一个物料需求
        /// 
        /// 工序物料详情的概念：
        /// 每个工序都需要消耗一些物料（投入），同时产出一些物料（产出）
        /// 
        /// 举例说明：
        /// 工序：手机组装工序
        /// 投入物料：
        /// - 手机主板 × 1个
        /// - 手机屏幕 × 1个  
        /// - 螺丝 × 8个
        /// - 胶水 × 2ml
        /// 产出物料：
        /// - 半成品手机 × 1个
        /// 副产品：
        /// - 废料塑料 × 0.1kg
        /// 
        /// 重要属性说明：
        /// - IOType：投入产出类型（Input投入/Output产出/ByProduct副产品等）
        /// - SequenceNumber：序号，用于排序显示
        /// - Quantity：使用量，表示需要多少数量
        /// - UsageRatio：用料比例，表示在总用料中的占比
        /// - LossRate：损耗率，考虑生产过程中的损耗
        /// - IsRequired：是否必需，区分必需物料和可选物料
        /// 
        /// 业务验证：
        /// 1. 工序必须存在且为启用状态
        /// 2. 物料必须存在
        /// 3. 同一工序中，序号不能重复
        /// 4. 同一工序中，同一物料的同一投入产出类型不能重复
        /// 5. 数量、比例、损耗率等数值必须在合理范围内
        /// </summary>
        /// <param name="dto">工序物料详情数据传输对象 - 包含工序ID、物料ID、用量等信息</param>
        /// <returns>操作结果 - 成功或失败信息</returns>
        public async Task<ApiResult> AddProcessStepMaterialDetailAsync(ProcessStepMaterialDetailDto dto)
        {
            try
            {
                // 步骤1：数据验证
                var validationResult = await ValidateProcessStepMaterialDetailDto(dto);
                if (!validationResult.IsSuc)
                {
                    return validationResult;
                }

                // 步骤2：验证工序是否存在且状态有效
                var processStep = await _processStepRepository.GetByIdAsync(dto.ProcessStepId);
                if (processStep == null)
                {
                    return ApiResult.Fail("指定的工序不存在", ResultCode.NotFound);
                }

                if (processStep.Status != StepStatus.Enabled)
                {
                    return ApiResult.Fail("工序未启用，无法添加物料详情", ResultCode.ValidationError);
                }

                // 步骤3：验证物料是否存在
                var material = await _dbContext.Db.Queryable<MaterialEntity>()
                    .Where(m => m.Id == dto.MaterialId)
                    .FirstAsync();

                if (material == null)
                {
                    return ApiResult.Fail("指定的物料不存在", ResultCode.NotFound);
                }

                // 步骤4：验证序号是否重复
                var existingSequence = await _dbContext.Db.Queryable<ProcessStepMaterialDetail>()
                    .Where(pmd => pmd.ProcessStepId == dto.ProcessStepId 
                               && pmd.SequenceNumber == dto.SequenceNumber 
                               && pmd.Version == dto.Version
                               && pmd.IsEnabled == true)
                    .AnyAsync();

                if (existingSequence)
                {
                    return ApiResult.Fail($"序号 {dto.SequenceNumber} 在版本 {dto.Version} 中已被使用", ResultCode.ValidationError);
                }

                // 步骤5：验证同一工序中同一物料是否重复
                var existingMaterial = await _dbContext.Db.Queryable<ProcessStepMaterialDetail>()
                    .Where(pmd => pmd.ProcessStepId == dto.ProcessStepId 
                               && pmd.MaterialId == dto.MaterialId 
                               && pmd.Version == dto.Version
                               && pmd.IOType == dto.IOType
                               && pmd.IsEnabled == true)
                    .AnyAsync();

                if (existingMaterial)
                {
                    return ApiResult.Fail($"该物料在版本 {dto.Version} 中已存在于此工序的{GetIOTypeName(dto.IOType)}列表", ResultCode.AlreadyExists);
                }

                // 步骤6：创建工序物料详情实体对象
                var materialDetail = new ProcessStepMaterialDetail
                {
                    Id = Guid.NewGuid(),
                    ProcessStepId = dto.ProcessStepId,
                    MaterialId = dto.MaterialId,
                    SequenceNumber = dto.SequenceNumber,
                    MaterialNumber = material.MaterialNumber ?? string.Empty,
                    MaterialName = material.MaterialName ?? string.Empty,
                    Specification = dto.Specification?.Trim(),
                    Unit = dto.Unit.Trim(),
                    Quantity = dto.Quantity,
                    UsageRatio = dto.UsageRatio,
                    IOType = dto.IOType,
                    LossRate = dto.LossRate,
                    IsRequired = dto.IsRequired,
                    IsEnabled = dto.IsEnabled,
                    Remark = dto.Remark?.Trim(),
                    Version = dto.Version.Trim(),
                    IsActive = dto.IsActive,
                    CreatedAt = DateTime.Now,
                    CreatedBy = "system"
                };

                // 步骤7：保存到数据库
                var insertResult = await _materialDetailRepository.InsertAsync(materialDetail);
                if (!insertResult)
                {
                    return ApiResult.Fail("保存工序物料详情失败", ResultCode.Error);
                }

                // 步骤8：如果设置为当前有效版本，需要将同一工序下的其他版本设为非活动状态
                if (dto.IsActive)
                {
                    await UpdateOtherMaterialDetailVersionsToInactive(dto.ProcessStepId, dto.Version);
                }

                return ApiResult.Success(ResultCode.Success);
            }
            catch (Exception ex)
            {
                return ApiResult.Fail($"添加工序物料详情失败: {ex.Message}", ResultCode.Error);
            }
        }

        /// <summary>
        /// 批量添加工序物料详情 - 一次性为工序添加多个物料需求
        /// 
        /// 批量操作的优势：
        /// 1. 性能优势：一次数据库连接处理多条记录，比多次单个操作快
        /// 2. 事务一致性：要么全部成功，要么全部失败，避免数据不一致
        /// 3. 用户体验：用户可以一次性配置完整的工序物料清单
        /// 
        /// 使用场景：
        /// 用户在配置工序时，需要一次性添加该工序的所有物料需求
        /// 比如配置"手机组装工序"时，一次性添加：
        /// - 主板、屏幕、电池、外壳等投入物料
        /// - 半成品手机等产出物料
        /// - 废料等副产品
        /// 
        /// 批量验证逻辑：
        /// 1. 批量数据内部验证：检查是否有重复的序号或物料
        /// 2. 单个数据验证：每条记录都要通过完整的业务验证
        /// 3. 数据库重复性验证：检查与现有数据是否冲突
        /// 4. 事务处理：使用数据库事务确保数据一致性
        /// 
        /// 错误处理：
        /// 如果任何一条记录验证失败，整个批量操作都会失败
        /// 这样确保数据的完整性和一致性
        /// </summary>
        /// <param name="batchDto">批量工序物料详情数据传输对象 - 包含工序ID和物料详情列表</param>
        /// <returns>操作结果 - 成功或失败信息</returns>
        public async Task<ApiResult> BatchAddProcessStepMaterialDetailsAsync(BatchProcessStepMaterialDetailDto batchDto)
        {
            try
            {
                // 步骤1：基础验证
                if (batchDto == null || batchDto.MaterialDetails == null || !batchDto.MaterialDetails.Any())
                {
                    return ApiResult.Fail("批量添加数据不能为空", ResultCode.ValidationError);
                }

                // 步骤2：验证工序是否存在且状态有效
                var processStep = await _processStepRepository.GetByIdAsync(batchDto.ProcessStepId);
                if (processStep == null)
                {
                    return ApiResult.Fail("指定的工序不存在", ResultCode.NotFound);
                }

                if (processStep.Status != StepStatus.Enabled)
                {
                    return ApiResult.Fail("工序未启用，无法添加物料详情", ResultCode.ValidationError);
                }

                // 步骤3：验证批量数据中的重复性
                var duplicateSequences = batchDto.MaterialDetails
                    .GroupBy(x => x.SequenceNumber)
                    .Where(g => g.Count() > 1)
                    .Select(g => g.Key)
                    .ToList();

                if (duplicateSequences.Any())
                {
                    return ApiResult.Fail($"批量数据中存在重复的序号: {string.Join(", ", duplicateSequences)}", ResultCode.ValidationError);
                }

                var duplicateMaterials = batchDto.MaterialDetails
                    .GroupBy(x => new { x.MaterialId, x.IOType })
                    .Where(g => g.Count() > 1)
                    .Select(g => g.Key)
                    .ToList();

                if (duplicateMaterials.Any())
                {
                    return ApiResult.Fail("批量数据中存在重复的物料", ResultCode.ValidationError);
                }

                // 步骤4：开始事务处理
                var materialDetails = new List<ProcessStepMaterialDetail>();
                var materialIds = batchDto.MaterialDetails.Select(x => x.MaterialId).Distinct().ToList();

                // 批量查询物料信息
                var materials = await _dbContext.Db.Queryable<MaterialEntity>()
                    .Where(m => materialIds.Contains(m.Id))
                    .ToListAsync();

                var materialDict = materials.ToDictionary(m => m.Id, m => m);

                // 步骤5：验证每个物料详情并创建实体
                foreach (var dto in batchDto.MaterialDetails)
                {
                    // 确保ProcessStepId一致
                    dto.ProcessStepId = batchDto.ProcessStepId;
                    dto.Version = batchDto.Version;
                    dto.IsActive = batchDto.IsActive;

                    // 验证单个DTO
                    var validationResult = await ValidateProcessStepMaterialDetailDto(dto);
                    if (!validationResult.IsSuc)
                    {
                        return ApiResult.Fail($"序号 {dto.SequenceNumber} 的物料数据验证失败: {validationResult.Msg}", ResultCode.ValidationError);
                    }

                    // 验证物料是否存在
                    if (!materialDict.ContainsKey(dto.MaterialId))
                    {
                        return ApiResult.Fail($"序号 {dto.SequenceNumber} 的物料不存在", ResultCode.NotFound);
                    }

                    var material = materialDict[dto.MaterialId];

                    // 创建实体对象
                    var materialDetail = new ProcessStepMaterialDetail
                    {
                        Id = Guid.NewGuid(),
                        ProcessStepId = dto.ProcessStepId,
                        MaterialId = dto.MaterialId,
                        SequenceNumber = dto.SequenceNumber,
                        MaterialNumber = material.MaterialNumber ?? string.Empty,
                        MaterialName = material.MaterialName ?? string.Empty,
                        Specification = dto.Specification?.Trim(),
                        Unit = dto.Unit.Trim(),
                        Quantity = dto.Quantity,
                        UsageRatio = dto.UsageRatio,
                        IOType = dto.IOType,
                        LossRate = dto.LossRate,
                        IsRequired = dto.IsRequired,
                        IsEnabled = dto.IsEnabled,
                        Remark = dto.Remark?.Trim(),
                        Version = dto.Version.Trim(),
                        IsActive = dto.IsActive,
                        CreatedAt = DateTime.Now,
                        CreatedBy = "system"
                    };

                    materialDetails.Add(materialDetail);
                }

                // 步骤6：检查数据库中的重复性
                var existingSequences = await _dbContext.Db.Queryable<ProcessStepMaterialDetail>()
                    .Where(pmd => pmd.ProcessStepId == batchDto.ProcessStepId 
                               && pmd.Version == batchDto.Version
                               && pmd.IsEnabled == true)
                    .Select(pmd => pmd.SequenceNumber)
                    .ToListAsync();

                var conflictSequences = batchDto.MaterialDetails
                    .Select(x => x.SequenceNumber)
                    .Intersect(existingSequences)
                    .ToList();

                if (conflictSequences.Any())
                {
                    return ApiResult.Fail($"以下序号在数据库中已存在: {string.Join(", ", conflictSequences)}", ResultCode.ValidationError);
                }

                // 检查物料重复性
                var existingMaterials = await _dbContext.Db.Queryable<ProcessStepMaterialDetail>()
                    .Where(pmd => pmd.ProcessStepId == batchDto.ProcessStepId 
                               && pmd.Version == batchDto.Version
                               && pmd.IsEnabled == true)
                    .Select(pmd => new { pmd.MaterialId, pmd.IOType })
                    .ToListAsync();

                var conflictMaterials = batchDto.MaterialDetails
                    .Select(x => new { x.MaterialId, x.IOType })
                    .Intersect(existingMaterials)
                    .ToList();

                if (conflictMaterials.Any())
                {
                    return ApiResult.Fail("批量数据中存在已添加的物料", ResultCode.ValidationError);
                }

                // 步骤7：批量插入数据
                var insertResult = await _materialDetailRepository.InsertRangeAsync(materialDetails);
                if (!insertResult)
                {
                    return ApiResult.Fail("批量保存工序物料详情失败", ResultCode.Error);
                }

                // 步骤8：如果设置为当前有效版本，需要将同一工序下的其他版本设为非活动状态
                if (batchDto.IsActive)
                {
                    await UpdateOtherMaterialDetailVersionsToInactive(batchDto.ProcessStepId, batchDto.Version);
                }

                return ApiResult.Success(ResultCode.Success);
            }
            catch (Exception ex)
            {
                return ApiResult.Fail($"批量添加工序物料详情失败: {ex.Message}", ResultCode.Error);
            }
        }

        /// <summary>
        /// 分页获取工序物料详情列表
        /// 功能：根据条件查询工序物料详情列表，支持分页显示和多种过滤条件
        /// </summary>
        /// <param name="processStepId">工序ID，如果为空则获取所有工序的物料详情</param>
        /// <param name="ioType">投入产出类型，如果为空则获取所有类型</param>
        /// <param name="pageIndex">页码，从1开始</param>
        /// <param name="pageSize">每页显示的记录数</param>
        /// <returns>分页的工序物料详情列表</returns>
        public async Task<ApiResult<PageResult<List<ProcessStepMaterialDetailListDto>>>> GetProcessStepMaterialDetailsPagedAsync(
            Guid? processStepId, MaterialIOType? ioType, int pageIndex, int pageSize)
        {
            try
            {
                // 步骤1：参数验证
                if (pageIndex < 1)
                {
                    return ApiResult<PageResult<List<ProcessStepMaterialDetailListDto>>>.Fail(
                        "页码必须大于0", ResultCode.ValidationError);
                }

                if (pageSize < 1 || pageSize > 1000)
                {
                    return ApiResult<PageResult<List<ProcessStepMaterialDetailListDto>>>.Fail(
                        "每页大小必须在1-1000之间", ResultCode.ValidationError);
                }

                // 步骤2：构建基础查询
                var query = _dbContext.Db.Queryable<ProcessStepMaterialDetail>()
                    .Where(pmd => pmd.IsEnabled == true); // 只查询启用的记录

                // 如果指定了工序ID，则添加过滤条件
                if (processStepId.HasValue && processStepId.Value != Guid.Empty)
                {
                    query = query.Where(pmd => pmd.ProcessStepId == processStepId.Value);
                }

                // 如果指定了投入产出类型，则添加过滤条件
                if (ioType.HasValue)
                {
                    query = query.Where(pmd => pmd.IOType == ioType.Value);
                }

                // 步骤3：获取符合条件的记录总数
                var totalCount = await query.CountAsync();

                // 步骤4：执行分页查询并关联相关表信息
                var data = await query
                    // 左连接工序表，获取工序信息
                    .LeftJoin<ProcessStep>((pmd, ps) => pmd.ProcessStepId == ps.Id)
                    // 左连接物料表，获取物料信息
                    .LeftJoin<MaterialEntity>((pmd, ps, m) => pmd.MaterialId == m.Id)
                    // 选择需要返回的字段，映射到ProcessStepMaterialDetailListDto
                    .Select((pmd, ps, m) => new ProcessStepMaterialDetailListDto
                    {
                        Id = pmd.Id,                                                    // 工序物料详情ID
                        ProcessStepId = pmd.ProcessStepId,                             // 工序ID
                        ProcessStepName = ps.ProcessStepName ?? string.Empty,          // 工序名称
                        MaterialId = pmd.MaterialId,                                   // 物料ID
                        SequenceNumber = pmd.SequenceNumber,                           // 序号
                        MaterialNumber = pmd.MaterialNumber,                           // 物料编号
                        MaterialName = pmd.MaterialName,                               // 物料名称
                        Specification = pmd.Specification,                             // 规格型号
                        Unit = pmd.Unit,                                               // 单位
                        Quantity = pmd.Quantity,                                       // 使用量
                        UsageRatio = pmd.UsageRatio,                                   // 用料比例
                        IOType = pmd.IOType,                                           // 投入产出类型
                        IOTypeName = GetIOTypeName(pmd.IOType),                        // 投入产出类型名称
                        LossRate = pmd.LossRate,                                       // 损耗率
                        IsRequired = pmd.IsRequired,                                   // 是否必需物料
                        IsEnabled = pmd.IsEnabled,                                     // 是否启用
                        Remark = pmd.Remark,                                           // 备注
                        Version = pmd.Version,                                         // 版本号
                        IsActive = pmd.IsActive,                                       // 是否当前有效版本
                        CreatedAt = pmd.CreatedAt                                      // 创建时间
                    })
                    // 排序：先按工序名称，再按投入产出类型，最后按序号
                    .OrderBy(dto => dto.ProcessStepName)
                    .OrderBy(dto => dto.IOType)
                    .OrderBy(dto => dto.SequenceNumber)
                    // 分页处理
                    .Skip((pageIndex - 1) * pageSize)                              // 跳过前面的记录
                    .Take(pageSize)                                                // 取指定数量的记录
                    .ToListAsync();                                                // 异步执行查询

                // 步骤5：构建分页结果对象
                var pageResult = new PageResult<List<ProcessStepMaterialDetailListDto>>
                {
                    Data = data,                                                    // 当前页的数据
                    TotalCount = totalCount,                                        // 总记录数
                    TotalPage = (int)Math.Ceiling(totalCount * 1.0 / pageSize)    // 总页数（向上取整）
                };

                // 步骤6：返回成功结果
                return ApiResult<PageResult<List<ProcessStepMaterialDetailListDto>>>.Success(pageResult, ResultCode.Success);
            }
            catch (Exception ex)
            {
                // 如果查询失败，返回错误信息
                return ApiResult<PageResult<List<ProcessStepMaterialDetailListDto>>>.Fail(
                    $"获取工序物料详情列表失败: {ex.Message}", ResultCode.Error);
            }
        }

        #endregion

        #region 私有辅助方法 - 内部使用的工具方法，不对外暴露

        /// <summary>
        /// 验证工序组成DTO的数据有效性 - 检查数据是否符合业务规则
        /// 
        /// 数据验证的重要性：
        /// 1. 防止脏数据进入数据库
        /// 2. 提供友好的错误提示
        /// 3. 确保业务规则的一致性
        /// 4. 提高系统的稳定性和可靠性
        /// 
        /// 验证层次：
        /// 1. 数据注解验证：使用[Required]、[StringLength]等注解进行基础验证
        /// 2. 业务规则验证：检查ID是否为空、版本号格式等业务逻辑
        /// 
        /// 为什么使用private async？
        /// - private：只在当前类内部使用，不对外暴露
        /// - async：支持异步操作，虽然当前方法没有异步调用，但保持接口一致性
        /// </summary>
        /// <param name="dto">工序组成数据传输对象 - 要验证的数据</param>
        /// <returns>验证结果 - 成功或包含错误信息的失败结果</returns>
        private async Task<ApiResult> ValidateProcessCompositionDto(ProcessCompositionDto dto)
        {
            // 第一层验证：使用数据注解进行基础验证
            // 数据注解是在DTO类上定义的验证规则，如[Required]、[StringLength]等
            var validationResults = new List<ValidationResult>();  // 存储验证结果的列表
            var validationContext = new ValidationContext(dto);    // 创建验证上下文
            
            // TryValidateObject方法会检查DTO上的所有数据注解
            // 参数说明：
            // - dto: 要验证的对象
            // - validationContext: 验证上下文
            // - validationResults: 验证失败时的错误信息会存储在这里
            // - true: 验证所有属性（包括嵌套属性）
            if (!Validator.TryValidateObject(dto, validationContext, validationResults, true))
            {
                // 如果验证失败，将所有错误信息合并成一个字符串
                var errors = string.Join("; ", validationResults.Select(vr => vr.ErrorMessage));
                return ApiResult.Fail($"数据验证失败: {errors}", ResultCode.ValidationError);
            }

            // 第二层验证：业务规则验证
            // 这些验证规则是业务逻辑特有的，无法通过数据注解表达
            
            // 验证工艺路线ID不能为空GUID
            // Guid.Empty表示全零的GUID（00000000-0000-0000-0000-000000000000）
            if (dto.ProcessRouteId == Guid.Empty)
            {
                return ApiResult.Fail("工艺路线ID不能为空", ResultCode.ValidationError);
            }

            // 验证工序ID不能为空GUID
            if (dto.ProcessStepId == Guid.Empty)
            {
                return ApiResult.Fail("工序ID不能为空", ResultCode.ValidationError);
            }

            // 验证版本号不能为空或空白字符串
            // IsNullOrWhiteSpace会检查null、空字符串、只包含空格的字符串
            if (string.IsNullOrWhiteSpace(dto.Version))
            {
                return ApiResult.Fail("版本号不能为空", ResultCode.ValidationError);
            }

            // 版本号长度验证（防止数据库字段溢出）
            if (dto.Version.Length > 50)
            {
                return ApiResult.Fail("版本号长度不能超过50个字符", ResultCode.ValidationError);
            }

            // 所有验证都通过，返回成功结果
            return ApiResult.Success(ResultCode.Success);
        }

        /// <summary>
        /// 将同一工艺路线下的其他版本设为非活动状态 - 版本管理的核心逻辑
        /// 
        /// 版本管理的业务需求：
        /// 在实际生产中，工艺路线会不断优化和改进，产生多个版本
        /// 但在同一时间，只能有一个版本是"当前活动版本"用于生产
        /// 
        /// 举例说明：
        /// 手机组装工艺路线有3个版本：
        /// - V1.0：最初版本（已过时）
        /// - V2.0：改进版本（已过时）  
        /// - V3.0：最新版本（当前活动）
        /// 
        /// 当V3.0设为活动版本时，V1.0和V2.0自动变为非活动状态
        /// 
        /// 实现逻辑：
        /// 1. 查找同一工艺路线下的其他版本
        /// 2. 将它们的IsActive字段设为false
        /// 3. 更新修改时间和修改人
        /// 4. 批量保存到数据库
        /// 
        /// 异常处理：
        /// 这个操作不应该影响主流程，所以异常只记录日志，不抛出
        /// </summary>
        /// <param name="processRouteId">工艺路线ID - 指定哪个工艺路线</param>
        /// <param name="currentVersion">当前版本号 - 新的活动版本，不会被设为非活动</param>
        private async Task UpdateOtherVersionsToInactive(Guid processRouteId, string currentVersion)
        {
            try // 使用try-catch确保异常不会影响主流程
            {
                // 步骤1：查找需要更新的记录
                // 查找条件：
                // 1. 同一个工艺路线（ProcessRouteId相同）
                // 2. 不是当前版本（Version不等于currentVersion）
                // 3. 当前是活动状态（IsActive为true）
                var otherVersions = await _dbContext.Db.Queryable<ProcessRouteStep>()
                    .Where(prs => prs.ProcessRouteId == processRouteId     // 同一工艺路线
                               && prs.Version != currentVersion           // 排除当前版本
                               && prs.IsActive == true)                   // 只查找活动版本
                    .ToListAsync();

                // 步骤2：如果找到了需要更新的记录，则进行批量更新
                if (otherVersions.Any()) // Any()检查集合是否包含元素
                {
                    // 遍历每个需要更新的记录
                    foreach (var item in otherVersions)
                    { 
                        item.IsActive = false;              // 设为非活动状态
                        item.UpdatedAt = DateTime.Now;      // 更新修改时间
                        item.UpdatedBy = "system";          // 记录修改者（实际应该是当前用户）
                    }

                    // 步骤3：批量更新到数据库
                    // Updateable()创建更新操作，ExecuteCommandAsync()执行更新
                    // 批量更新比逐个更新效率更高
                    await _dbContext.Db.Updateable(otherVersions).ExecuteCommandAsync();
                }
            }
            catch (Exception ex)
            {
                // 异常处理策略：
                // 1. 不抛出异常，避免影响主业务流程
                // 2. 记录异常信息，便于后续排查问题
                // 3. 实际项目中应该使用专业的日志框架（如NLog、Serilog）
                
                // Console.WriteLine只是临时方案，生产环境应该使用日志框架
                Console.WriteLine($"更新其他版本状态时发生异常: {ex.Message}");
                
                // 可以考虑的其他处理方式：
                // - 记录到日志文件
                // - 发送告警通知
                // - 记录到错误日志表
                // 但不应该抛出异常中断主流程
            }
        }

        /// <summary>
        /// 验证工序物料详情DTO的数据有效性
        /// </summary>
        /// <param name="dto">工序物料详情数据传输对象</param>
        /// <returns>验证结果</returns>
        private async Task<ApiResult> ValidateProcessStepMaterialDetailDto(ProcessStepMaterialDetailDto dto)
        {
            // 使用数据注解进行基础验证
            var validationResults = new List<ValidationResult>();
            var validationContext = new ValidationContext(dto);

            if (!Validator.TryValidateObject(dto, validationContext, validationResults, true))
            {
                var errors = string.Join("; ", validationResults.Select(vr => vr.ErrorMessage));
                return ApiResult.Fail($"数据验证失败: {errors}", ResultCode.ValidationError);
            }

            // 业务规则验证
            if (dto.ProcessStepId == Guid.Empty)
            {
                return ApiResult.Fail("工序ID不能为空", ResultCode.ValidationError);
            }

            if (dto.MaterialId == Guid.Empty)
            {
                return ApiResult.Fail("物料ID不能为空", ResultCode.ValidationError);
            }

            if (string.IsNullOrWhiteSpace(dto.Unit))
            {
                return ApiResult.Fail("单位不能为空", ResultCode.ValidationError);
            }

            if (dto.Quantity <= 0)
            {
                return ApiResult.Fail("使用量必须大于0", ResultCode.ValidationError);
            }

            if (dto.UsageRatio < 0 || dto.UsageRatio > 100)
            {
                return ApiResult.Fail("用料比例必须在0-100之间", ResultCode.ValidationError);
            }

            if (dto.LossRate < 0 || dto.LossRate > 100)
            {
                return ApiResult.Fail("损耗率必须在0-100之间", ResultCode.ValidationError);
            }

            return ApiResult.Success(ResultCode.Success);
        }

        /// <summary>
        /// 将同一工序下的其他版本设为非活动状态
        /// </summary>
        /// <param name="processStepId">工序ID</param>
        /// <param name="currentVersion">当前版本号</param>
        private async Task UpdateOtherMaterialDetailVersionsToInactive(Guid processStepId, string currentVersion)
        {
            try
            {
                var otherVersions = await _dbContext.Db.Queryable<ProcessStepMaterialDetail>()
                    .Where(pmd => pmd.ProcessStepId == processStepId 
                               && pmd.Version != currentVersion 
                               && pmd.IsActive == true)
                    .ToListAsync();

                if (otherVersions.Any())
                {
                    foreach (var item in otherVersions)
                    {
                        item.IsActive = false;
                        item.UpdatedAt = DateTime.Now;
                        item.UpdatedBy = "system";
                    }

                    await _dbContext.Db.Updateable(otherVersions).ExecuteCommandAsync();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"更新其他版本状态时发生异常: {ex.Message}");
            }
        }

        /// <summary>
        /// 获取投入产出类型的中文名称 - 将枚举值转换为用户友好的中文显示
        /// 
        /// 为什么需要这个方法？
        /// 1. 数据库中存储的是枚举值（如MaterialIOType.Input）
        /// 2. 前端界面需要显示中文名称（如"投入"）
        /// 3. 提供统一的转换逻辑，避免在多处重复代码
        /// 
        /// 投入产出类型说明：
        /// - Input（投入）：工序消耗的原材料、半成品等
        /// - Output（产出）：工序生产的成品、半成品等
        /// - ByProduct（副产品）：生产过程中产生的有价值副产品
        /// - Scrap（废料）：生产过程中产生的废弃物料
        /// - Recyclable（可回收）：可以回收再利用的物料
        /// 
        /// switch表达式语法（C# 8.0新特性）：
        /// 这是一种简洁的模式匹配语法，比传统的switch语句更简洁
        /// _ => "未知" 表示默认情况，处理未定义的枚举值
        /// </summary>
        /// <param name="ioType">投入产出类型枚举值</param>
        /// <returns>对应的中文名称字符串</returns>
        private static string GetIOTypeName(MaterialIOType ioType)
        {
            // 使用switch表达式进行模式匹配
            // 每个case都是 枚举值 => 返回值 的格式
            return ioType switch
            {
                MaterialIOType.Input => "投入",        // 投入物料
                MaterialIOType.Output => "产出",       // 产出物料
                MaterialIOType.ByProduct => "副产品",   // 副产品
                MaterialIOType.Scrap => "废料",        // 废料
                MaterialIOType.Recyclable => "可回收", // 可回收物料
                _ => "未知"                            // 默认情况，处理未知的枚举值
            };
        }

        /// <summary>
        /// 验证工艺路线DTO的数据有效性
        /// </summary>
        /// <param name="dto">工艺路线数据传输对象</param>
        /// <returns>验证结果</returns>
        private async Task<ApiResult> ValidateProcessRouteDto(ProcessRouteDto dto)
        {
            // 使用数据注解进行基础验证
            var validationResults = new List<ValidationResult>();
            var validationContext = new ValidationContext(dto);

            if (!Validator.TryValidateObject(dto, validationContext, validationResults, true))
            {
                var errors = string.Join("; ", validationResults.Select(vr => vr.ErrorMessage));
                return ApiResult.Fail($"数据验证失败: {errors}", ResultCode.ValidationError);
            }

            // 业务规则验证
            if (string.IsNullOrWhiteSpace(dto.ProcessRouteNumber))
            {
                return ApiResult.Fail("工艺路线编号不能为空", ResultCode.ValidationError);
            }

            if (string.IsNullOrWhiteSpace(dto.ProcessRouteName))
            {
                return ApiResult.Fail("工艺路线名称不能为空", ResultCode.ValidationError);
            }

            // 状态验证
            var validStatuses = new[] { "启用", "禁用" };
            if (!validStatuses.Contains(dto.Status))
            {
                return ApiResult.Fail("状态只能是'启用'或'禁用'", ResultCode.ValidationError);
            }

            // 工艺路线编号格式验证（可根据实际需求调整）
            if (dto.ProcessRouteNumber.Length < 2)
            {
                return ApiResult.Fail("工艺路线编号长度不能少于2个字符", ResultCode.ValidationError);
            }

            return ApiResult.Success(ResultCode.Success);
        }

        /// <summary>
        /// 将同一编号下的其他版本设为非活动状态
        /// </summary>
        /// <param name="processRouteNumber">工艺路线编号</param>
        /// <param name="currentRouteId">当前工艺路线ID</param>
        private async Task UpdateOtherProcessRouteVersionsToInactive(string processRouteNumber, Guid currentRouteId)
        {
            try
            {
                // 查找同一编号下的其他版本
                var otherVersions = await _dbContext.Db.Queryable<ProcessRouteEntity>()
                    .Where(pr => pr.ProcessRouteNumber == processRouteNumber 
                               && pr.Id != currentRouteId 
                               && pr.IsActive == true)
                    .ToListAsync();

                if (otherVersions.Any())
                {
                    foreach (var item in otherVersions)
                    {
                        item.IsActive = false;
                        //item.LastUpdatedTime = DateTime.Now;
                        item.UpdatedAt = DateTime.Now;
                        item.UpdatedBy = "system";
                    }

                    await _dbContext.Db.Updateable(otherVersions).ExecuteCommandAsync();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"更新其他版本状态时发生异常: {ex.Message}");
            }
        }

        /// <summary>
        /// 验证工序DTO的数据有效性
        /// </summary>
        /// <param name="dto">工序数据传输对象</param>
        /// <returns>验证结果</returns>
        private async Task<ApiResult> ValidateProcessStepDto(ProcessStepDto dto)
        {
            // 使用数据注解进行基础验证
            var validationResults = new List<ValidationResult>();
            var validationContext = new ValidationContext(dto);

            if (!Validator.TryValidateObject(dto, validationContext, validationResults, true))
            {
                var errors = string.Join("; ", validationResults.Select(vr => vr.ErrorMessage));
                return ApiResult.Fail($"数据验证失败: {errors}", ResultCode.ValidationError);
            }

            // 业务规则验证
            if (string.IsNullOrWhiteSpace(dto.ProcessStepNumber))
            {
                return ApiResult.Fail("工序编号不能为空", ResultCode.ValidationError);
            }

            if (string.IsNullOrWhiteSpace(dto.ProcessStepName))
            {
                return ApiResult.Fail("工序名称不能为空", ResultCode.ValidationError);
            }

            // 工序编号格式验证（可根据实际需求调整）
            if (dto.ProcessStepNumber.Length < 2)
            {
                return ApiResult.Fail("工序编号长度不能少于2个字符", ResultCode.ValidationError);
            }

            // 颜色格式验证（如果提供了颜色）
            if (!string.IsNullOrWhiteSpace(dto.Color) && !dto.Color.StartsWith("#"))
            {
                return ApiResult.Fail("颜色格式应为#开头的十六进制值，如#1890FF", ResultCode.ValidationError);
            }

            return ApiResult.Success(ResultCode.Success);
        }

        /// <summary>
        /// 获取工序状态的中文名称 - 将工序状态枚举转换为中文显示
        /// 
        /// 工序状态说明：
        /// - Enabled（启用）：工序可以正常使用，可以被添加到工艺路线中
        /// - Disabled（禁用）：工序暂停使用，不能被添加到新的工艺路线中
        /// 
        /// 状态管理的作用：
        /// 1. 灵活控制工序的可用性
        /// 2. 支持工序的生命周期管理
        /// 3. 避免删除工序导致的数据完整性问题
        /// 
        /// 为什么使用static？
        /// 这个方法不依赖于类的实例状态，是一个纯函数
        /// 使用static可以提高性能，避免不必要的对象创建
        /// </summary>
        /// <param name="status">工序状态枚举值</param>
        /// <returns>对应的中文名称字符串</returns>
        private static string GetStepStatusName(StepStatus status)
        {
            return status switch
            {
                StepStatus.Enabled => "启用",   // 工序启用状态
                StepStatus.Disabled => "禁用",  // 工序禁用状态
                _ => "未知"                     // 未知状态（容错处理）
            };
        }

        #endregion

        #region 通用辅助方法 - 减少代码重复

        /// <summary>
        /// 通用DTO验证方法 - 使用数据注解和自定义验证逻辑验证任何DTO
        /// </summary>
        /// <typeparam name="T">DTO类型</typeparam>
        /// <param name="dto">要验证的DTO对象</param>
        /// <param name="customValidation">自定义验证逻辑的委托，可以为null</param>
        /// <returns>验证结果</returns>
        private ApiResult ValidateDto<T>(T dto, Func<T, ApiResult> customValidation = null) where T : class
        {
            // 使用数据注解进行基础验证
            var validationResults = new List<ValidationResult>();
            var validationContext = new ValidationContext(dto);

            if (!Validator.TryValidateObject(dto, validationContext, validationResults, true))
            {
                var errors = string.Join("; ", validationResults.Select(vr => vr.ErrorMessage));
                return ApiResult.Fail($"数据验证失败: {errors}", ResultCode.ValidationError);
            }

            // 如果提供了自定义验证逻辑，则执行
            if (customValidation != null)
            {
                return customValidation(dto);
            }

            return ApiResult.Success(ResultCode.Success);
        }

        /// <summary>
        /// 通用版本管理方法 - 将同一实体的其他版本设为非活动状态
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="queryable">查询表达式</param>
        /// <param name="updateAction">更新实体的操作</param>
        /// <returns>异步任务</returns>
        private async Task UpdateOtherVersionsToInactiveGeneric<T>(ISugarQueryable<T> queryable, Action<T> updateAction = null) where T : class, new()
        {
            try
            {
                // 查找需要更新的记录
                var otherVersions = await queryable.ToListAsync();

                if (otherVersions.Any())
                {
                    // 遍历每个需要更新的记录
                    foreach (var item in otherVersions)
                    {
                        // 默认更新操作：设置IsActive为false
                        var entity = item as dynamic;
                        entity.IsActive = false;
                        entity.UpdatedAt = DateTime.Now;
                        entity.UpdatedBy = "system";

                        // 如果提供了自定义更新操作，则执行
                        updateAction?.Invoke(item);
                    }

                    // 批量更新到数据库
                    await _dbContext.Db.Updateable(otherVersions).ExecuteCommandAsync();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"更新其他版本状态时发生异常: {ex.Message}");
            }
        }

        #endregion
    }
}
